/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * loader.c: Implementierung der Loader-Steuerung
 */

#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <time.h>
#include "loader.h"
#include "catalog.h"
#include "user.h"
#include "score.h"
#include "common/question.h"
#include "common/rfc.h"

// Pipes
int iReadPipe[2]; //  Input to loader process
int iWritePipe[2]; // Output from loader process

// Modulglobale Variablen
size_t iSharedMemory_Size;
void* oSharedMemory = NULL;
int iQuestionsCount = 0;

// Starts the loader process and reads the catalogs
// Returns 0 if successfull, -1 otherwise
int loader_StartLoader() {
	// create loader process
	if (loader_ForkLoaderProcess() < 0) {
		debugPrint("\nError creating loader process.");
		return -1;
	}

	// read catalogs
	if (loader_ReadCatalogsFromLoader() < 0) {
		debugPrint("\nError reading catalogs.");
		return -1;
	}

	return 0;
}

// Forks the main Process to run a loader child process
// Returns <= 0 if successfull, otherwise < 0
int loader_ForkLoaderProcess() {
	int iPID;

	// create pipes
	if ((pipe(iReadPipe) < 0) || (pipe(iWritePipe) < 0)) {
		errorPrint("\nPipe konnte nicht erstellt werden!");
		return LOADER_PIPECREATEERROR;
	}

	// fork loader process
	iPID = fork();
	if (iPID < 0) {
		// error forking process
		fprintf(stderr, "\nError while forking");
		return LOADER_PROCESSCREATIONERROR;
	}
	else if (iPID == 0) {
		// child process
		// redirect std input
		if (dup2(iReadPipe[0], 0) == -1) {
			return -1;
		}
		// redirect std output
		if (dup2(iWritePipe[1], 1) == -1) {
			return -1;
		}

		// close unneeded pipes on child side (since we redirected them to standard pipes)
		close(iReadPipe[0]);
		close(iReadPipe[1]);
		close(iWritePipe[0]);
		close(iWritePipe[1]);

		// start loader in the new process
		execl("loader", "loader", "-d", catalog_GetCatalogDir(), NULL);
		fprintf(stderr, "\nReturning from loader, errno: %d: \n", errno);
		return -1;
	}
	else {
		// parent process
		// close unneeded pipes (since we are on the parent side)
		close(iReadPipe[0]);
		close(iWritePipe[1]);
	}
	return iPID;
}

// Sends the browse command to the loader and reads the returned catalogs
// Returns 0 if successfull,  < 0 otherwise
int loader_ReadCatalogsFromLoader() {
	char *sBuffer;

	// send browse command to loader
	if (loader_WriteToPipe(BROWSE_CMD, strlen(BROWSE_CMD)) < 0) {
		return LOADER_PIPESENDERROR;
	}

	// receive catalogs
	sBuffer = readLine(iWritePipe[0]);
	if ((sBuffer == NULL) || (strcmp(sBuffer, "") == 0)) {
		// first read failed, return error
		errorPrint("\nError reading first entry");
		return LOADER_PIPESRECEIVEERROR;
	}
	while (sBuffer != NULL && strcmp(sBuffer, "") != 0) {
		// add catalog
		catalog_AddNewCatalog(sBuffer);
		// read next line
		sBuffer = readLine(iWritePipe[0]);
	}
	return 0;
}

// Writes a given sMessage with length iLength to iPipe
// Returns LOADER_PIPESENDSUCCESS if successfull, LOADER_PIPESENDERROR otherwise
int loader_WriteToPipe(char* sMessage, size_t iLength) {
	// write to pipe
	if (write(iReadPipe[1], sMessage, iLength) < iLength) {
		debugPrint("\nSenden der Nachricht über Pipe fehlgeschlagen: ");
		//perror("write");
		return LOADER_PIPESENDERROR;
	}
	// write \n
	if (write(iReadPipe[1], "\n", 1) < 1) {
		debugPrint("Senden der Nachricht über Pipe fehlgeschlagen: ");
		//perror("write");
		return LOADER_PIPESENDERROR;
	}
	return LOADER_PIPESENDSUCCESS;
}


// Loads a catalog with a given sCatalogName to the shared memory
// Returns 0 if successfull, < 0 otherwise
int loader_LoadCatalog(char *sCatalogName, int iCatalogLen) {
		char *sMessage, *sRecieveMessage;
		int iMessageLen;
		char cTemp;
		int i;

		// allocate memory for loader message
		iMessageLen = (strlen(LOAD_CMD_PREFIX) + iCatalogLen);
		sMessage = malloc((iMessageLen + 1) * sizeof(char));
		sRecieveMessage = malloc(512 * sizeof(char));

		debugPrint("\nMessage preparing... Prefix: %s | CatLen: %d | MessLen: %d", sCatalogName, iCatalogLen, iMessageLen);

		// string message together
		strcpy(sMessage, LOAD_CMD_PREFIX);
		strncat(sMessage + strlen(LOAD_CMD_PREFIX), sCatalogName, iCatalogLen);
		sMessage[iMessageLen] = 0;

		debugPrint("\nMessage created: %s", sMessage);


		debugPrint("\nMessage sent!");

		write(iReadPipe[1], sMessage, strlen(sMessage));
		write(iReadPipe[1],"\n",1);



		strcpy(sRecieveMessage, readLine(iWritePipe[0]));


		debugPrint("\nRead[0]: %s\n", sRecieveMessage);


		if (sRecieveMessage == NULL) {
			debugPrint("Pipe Fehler!");
			return LOADER_PIPEERROR;
		}
		else if (strncmp(sRecieveMessage, LOAD_SUCCESS_PREFIX, strlen(LOAD_SUCCESS_PREFIX)) == 0) {
			debugPrint("Fragekatalog geladen!");
			iQuestionsCount = atoi(&sRecieveMessage[strlen(LOAD_SUCCESS_PREFIX)]);
			loader_OpenSharedMemory(iQuestionsCount);
		}
		else
			debugPrint("Konnte Fragekatalog nicht laden!");


		return 0;

}

// Opens the shared memory, mapping it to global oSharedMemory
// Returns 0 if successfull, -1 otherwise
int loader_OpenSharedMemory(int iAmountQuestions) {
		int iHandle;

		iSharedMemory_Size = iAmountQuestions * sizeof(Question);

		// get handle on shared mem
		iHandle = shm_open(SHMEM_NAME, O_RDONLY, 0);
		if (iHandle == -1) {
			// error occured
			switch (errno) {
				case EEXIST:
					debugPrint("Error opening shared memory: does not exist.");
					break;
				default:
					debugPrint("Error opening shared memory.");
					break;
			}
			return -1;
		}

		// map shared mem
		oSharedMemory = mmap(NULL, iSharedMemory_Size, PROT_READ, MAP_SHARED, iHandle, 0);

		// check for fail
		if (oSharedMemory == MAP_FAILED) {
			debugPrint("Error mapping shared memory.", strerror(errno));
			close(iHandle);
			shm_unlink(SHMEM_NAME);
			return -1;
		}

		return 0;
}

Question* loader_GetQuestion(int iQIndex) {
	Question* oQuestion;
	// Check Index
	debugPrint("\nQIndex: %d | QCount: %d", iQIndex, iQuestionsCount);
	if (iQIndex >= iQuestionsCount) {
		return NULL;
	}

	oQuestion = oSharedMemory + (sizeof(Question) * iQIndex);
	return oQuestion;
}

void loader_UnlinkSharedMemory() {
	if (shm_unlink(SHMEM_NAME) == 0) {
		debugPrint("\nShared memory unlinked");
	}
}

int loader_SendNextQuestion(int iUserID) {
	int i;
	int iCount;
	int iLength;
	int iMessageLen;
	char *sMessage;
	struct UserInfo *oUser = user_GetUserList();
	Question *oQuestion;
	time_t oStartTime;
	time_t oEndTime;

	iCount = user_GetUserCount();
	// Search for UserID
	for (i = 0; i < iCount; i++) {
		if (oUser -> iUserID == iUserID) {
			// User found!
			debugPrint("\nUser ID %d found! Name: %s | QIndex: %d", iUserID, oUser -> sUsername, oUser -> iQuestionIndex);
			oUser -> iQuestionIndex++;
			debugPrint("\nNew Index: %d", oUser -> iQuestionIndex);
			oQuestion = loader_GetQuestion(oUser -> iQuestionIndex);
			if (oQuestion == NULL) {
				// end
				debugPrint("No Question loaded");
				iLength = 2;
				sMessage = malloc(iLength * sizeof(char));
				sMessage[0] = 0;
				sMessage[1] = 0;
				oUser -> bEndphaseAktiv = 1;
				loader_CheckGameEnd();
			} else {
				// send question
				debugPrint("Question loaded");
				iLength = 771;
				sMessage = malloc(iLength * sizeof(char));
				sMessage[0] = (uint8_t)((iLength-2) / 256);
				sMessage[1] = (uint8_t)(iLength-2);
				memmove(sMessage + 2, oQuestion -> question, 256);
				memmove(sMessage + 258, oQuestion -> answers[0], 128);
				memmove(sMessage + 386, oQuestion -> answers[1], 128);
				memmove(sMessage + 514, oQuestion -> answers[2], 128);
				memmove(sMessage + 642, oQuestion -> answers[3], 128);
				memmove(sMessage + 770, &oQuestion -> timeout, 1);
			}
			server_SendServerMessage(oUser -> iSocket, server_CreateMessage(TYPE_IS_QUESTION, sMessage, iLength));

			// wait for timeout


			if (oQuestion == NULL) {
				oUser -> iTimeoutRemaining = 0;
				return 1;
			}

			// Benötigte Strukturen
			fd_set oContainer;
			struct timespec oTimeout;

			// Timeout setzen
			oTimeout.tv_sec = oQuestion -> timeout;
			oTimeout.tv_nsec = 0;

			// Menge löschen
			FD_ZERO(&oContainer);

			// Socket zur Menge hinzufügen
			FD_SET(oUser -> iSocket, &oContainer);

			// Auf Antwort warten
			oStartTime = time(NULL);
			infoPrint("=== BEFORE SELECT : %s", oUser->sUsername);
			iCount = select(oUser -> iSocket + 1, &oContainer, NULL, NULL, &oTimeout);
			infoPrint("\ngeht weiter (%d) : %s", iCount,  oUser->sUsername);
			if (iCount < 0) {
				// error
				infoPrint("\nERROR ERROR ERROR ERROR ERROR : %s", oUser->sUsername);
			} else if (iCount == 0) {

				// timed out
				oUser -> iTimeoutRemaining = 0;
				loader_SendTimeoutPackage(iUserID);
			} else if (iCount > 0) {
				// timeout nicht erreicht
				oEndTime = time(NULL);
				iCount = (oQuestion->timeout * 10) - (int)(10.0 * difftime(oEndTime, oStartTime));
				oUser -> iTimeoutRemaining = iCount;
				//oUser -> iPunktestand += iCount;
			}
			return 1;
		}
		oUser = oUser -> ptrNext;
	}
	return 0;
}

int loader_SendTimeoutPackage(int iUserID) {
	int i;
	int iCount;
	int iLength;
	int iMessageLen;
	char *sMessage;
	struct UserInfo *oUser = user_GetUserList();
	Question *oQuestion;

	iCount = user_GetUserCount();
	// Search for UserID
	for (i = 0; i < iCount; i++) {
		if (oUser -> iUserID == iUserID) {
			// User found!

			oQuestion = loader_GetQuestion(oUser -> iQuestionIndex);
			if (oQuestion != NULL) {
				sMessage = malloc(4 * sizeof(char));
				sMessage[0] = (uint8_t)(0);
				sMessage[1] = (uint8_t)(2);
				sMessage[2] = (uint8_t)(1);
				memmove(sMessage + 3, &oQuestion -> correct, 1);
				server_SendServerMessage(oUser -> iSocket, server_CreateMessage(TYPE_IS_QUESTION_RESULT, sMessage, 4));
			}
			return 1;
		}
		oUser = oUser -> ptrNext;
	}
	return 0;
}


int loader_checkAnswer(int iUserID, int selectedIndex) {
	int i;
	int iCount;
	int iLength;
	int iMessageLen;
	char *sMessage;
	struct UserInfo *oUser = user_GetUserList();
	Question *oQuestion;

	iCount = user_GetUserCount();
	// Search for UserID
	for (i = 0; i < iCount; i++) {
		if (oUser -> iUserID == iUserID) {
			// User found!
			debugPrint("\nUser ID %d found! Name: %s | QIndex: %d", iUserID, oUser -> sUsername, oUser -> iQuestionIndex);
			debugPrint("\nNew Index: %d", oUser -> iQuestionIndex);
			oQuestion = loader_GetQuestion(oUser -> iQuestionIndex);
			if (oQuestion != NULL) {
				if(oQuestion -> correct == selectedIndex) {
					// add points
					oUser -> iPunktestand += oUser -> iTimeoutRemaining;
					sem_post(scoret_GetScoreSemaphore());
				}
				sMessage = malloc(4 * sizeof(char));
				sMessage[0] = (uint8_t)(0);
				sMessage[1] = (uint8_t)(2);
				sMessage[2] = (uint8_t)(0);
				memmove(sMessage + 3, &oQuestion -> correct, 1);
				server_SendServerMessage(oUser -> iSocket, server_CreateMessage(TYPE_IS_QUESTION_RESULT, sMessage, 4));
			}
			return 1;
		}
		oUser = oUser -> ptrNext;
	}
	return 0;
}

int loader_SendGameOver() {
	int i;
	int iCount;
	char *sMessage;
	struct UserInfo *oUser = user_GetUserList();

	iCount = user_GetUserCount();
	// Search for UserID
	for (i = 0; i < iCount; i++) {
		sMessage = malloc(3 * sizeof(char));
		sMessage[0] = (uint8_t)(0);
		sMessage[1] = (uint8_t)(1);
		sMessage[2] = (uint8_t)(i + 1);
		server_SendServerMessage(oUser -> iSocket, server_CreateMessage(TYPE_IS_GAME_OVER, sMessage, 3));
		oUser = oUser -> ptrNext;
		free(sMessage);
	}


	return 0;
}

int loader_CheckGameEnd() {
	int i,check = 1;
	int iCount;
	struct UserInfo *oUser = user_GetUserList();

	iCount = user_GetUserCount();
	for (i = 0; i < iCount; i++) {
			if(oUser->bEndphaseAktiv == 0) {
				check = 0;
			}
			oUser = oUser -> ptrNext;
		}
		if(check == 1) {
			loader_SendGameOver();
			sleep(1);
			exit(0);
			return 1;
		}
		return 0;
}

